package cn.com.utils.crypto;

import cn.com.consts.MsgCd;
import cn.com.utils.WebUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;


/**
 * 该类提供了一组静态方法，用于使用密钥对数据进行加密和解密
 */
@Slf4j
public class KeyCryptoUtil {

    private static final String KEY_PATH = "cloudstar.key.path";

    private static final String PUBLIC_KEY;

    private static final String PRIVATE_KEY;

    /**
     * 指定加密算法RSA非对称加密
     */
    private static final String RSA = "key.rsa";

    /**
     * 指定RSA非对称算法/ECB模式/填充方式（私钥加密，公钥解密专用）
     */
    private static final String RSA_CIPHER = "key.rsa.cipher";


    private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;

    /*
     * 初始化密码器
     */
    static {
        // 公钥用明文
        PUBLIC_KEY = getKeyStr("pub");
        PRIVATE_KEY = getKeyStr("pri");
    }

    /**
     * 获取公钥
     *
     * @return {@link String}
     */
    public static String getPublicKey() {
        return PUBLIC_KEY;
    }


    /**
     * 从 base64 解码，然后使用 RSA 算法从解码的字符串生成公钥
     *
     * @param key 公钥字符串
     * @return 一个 PublicKey 对象。
     */
    static RSAPublicKey toPublicKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory kf = KeyFactory.getInstance(SettingUtils.get(RSA));
        return (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(key)));
    }

    /**
     * 私钥解密
     *
     * @param cipherStr 加密的字符串
     * @param publicKey 用于解密密文的公钥。
     * @return 解密后的字符串。
     */
    static String decryptPub(String cipherStr, String publicKey) {
        String result = null;
        try {
            Cipher cipher = Cipher.getInstance(SettingUtils.get(RSA_CIPHER), new BouncyCastleProvider());
            RSAPrivateKey key = toPrivateKey(publicKey);
            cipher.init(Cipher.DECRYPT_MODE, key);
            result = new String(rsaSplitCode(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(cipherStr),
                    key.getModulus().bitLength()), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        // 如果为空返回原文
        if (ObjectUtil.isEmpty(result)) {
            return cipherStr;
        }
        return result;
    }

    static String getPrivateKey() {
        return PRIVATE_KEY;
    }

    /**
     * 从 base64 解码，然后使用 RSA 算法从解码后的字符串生成私钥
     *
     * @param key 私钥字符串
     * @return 一个 PrivateKey 对象。
     */
    static RSAPrivateKey toPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory kf = KeyFactory.getInstance(SettingUtils.get(RSA));
        return (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)));
    }

    /**
     * 使用公钥加密内容。
     *
     * @param content   要加密的内容
     * @param publicKey 用于加密数据的公钥。
     * @return 加密的字符串
     */
    static String encrypt(String content, String publicKey) {
        String result = null;
        try {
            byte[] dataByte = content.getBytes(StandardCharsets.UTF_8);
            Cipher cipher = Cipher.getInstance(SettingUtils.get(RSA_CIPHER));
            RSAPublicKey key = toPublicKey(publicKey);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            result = new String(rsaSplitCode(cipher,
                    Cipher.ENCRYPT_MODE,
                    dataByte,
                    key.getModulus().bitLength()));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 获取密钥字符串
     *
     * @param fileName 文件名文件名
     * @return {@link String}
     */
    @SneakyThrows
    static String getKeyStr(String fileName) {
        final String keyPath = System.getProperty(KEY_PATH, SettingBurstUtils.get(KEY_PATH));
        final File file = FileUtil.file(keyPath);
        if (file.isDirectory()) {
            final File[] files = file.listFiles();
            if (files == null) {
                throw new FileNotFoundException(WebUtil.getMessage(MsgCd.ERR_MSG_BSS_1638171384));
            }
            for (File f : files) {
                if (f.getName().equalsIgnoreCase(fileName)) {
                    return FileUtil.readString(f, StandardCharsets.UTF_8);
                }
            }
        }
        return null;
    }


    /**
     * RSA分段加解密
     *
     * @param cipher  密码
     * @param mode    模式
     * @param data    数据
     * @param keySize Key大小
     * @return {@link byte[]}
     */
    static byte[] rsaSplitCode(Cipher cipher, int mode, byte[] data, int keySize) {
        // 最大块
        int maxBlock = 0;
        switch (mode) {
            case Cipher.ENCRYPT_MODE:
                maxBlock = keySize / Byte.SIZE - OAEP_MGF1_SHA256_OVERHEAD;
                break;
            case Cipher.DECRYPT_MODE:
                maxBlock = keySize / Byte.SIZE;
                break;
            default:
                throw new IllegalStateException(WebUtil.getMessage(MsgCd.ERR_MSG_BSS_2113870405) + mode);
        }
        ByteArrayOutputStream ctStream = null;
        try {
            ctStream = new ByteArrayOutputStream();
            int off = 0;
            try {
                while (off < data.length) {
                    int toCrypt = Math.min(maxBlock, data.length - off);
                    byte[] partialCT = cipher.doFinal(data, off, toCrypt);
                    ctStream.write(partialCT);
                    off += toCrypt;
                }
            } catch (Exception e) {
                log.error("Encryption and decryption threshold:{}, error info: ", off, e);
            }
            byte[] result = ctStream.toByteArray();
            IoUtil.close(ctStream);
            return result;
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            IoUtil.close(ctStream);
        }
        return null;
    }

}
